package com.my.app.document.service;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.aop.SpringProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.my.annotation.Param;
import com.my.annotation.Remark;
import com.my.annotation.Return;
import com.my.app.document.model.ApiInfo;
import com.my.app.document.model.ApiParameter;
import com.my.app.document.model.ApiResult;
import com.my.framework.web.annotation.Page;
import com.my.framework.web.annotation.Query;
import com.my.framework.web.annotation.Update;
import com.my.model.Request;
import com.my.model.Response;
import com.my.utils.ClassUtil;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class DocumentService {
	private volatile List<ApiInfo> apiList;

	@Autowired
	private ApplicationContext content;

	// 扫描系统所有Controller,解析所有设置了RequestMapping的方法
	public List<ApiInfo> getApiInfo() {
		if (apiList != null) {
			return apiList;
		}
		ReentrantLock lock = new ReentrantLock();
		lock.lock();
		if (apiList != null) {
			return apiList;
		}

		try {
			Map<String, Object> beans = content.getBeansWithAnnotation(Controller.class);
			List<ApiInfo> result = new ArrayList();
			beans.forEach((name, obj) -> {
				Class clazz = obj.getClass();
				List apis = parse(clazz);
				result.addAll(apis);
			});
			
			
			//验证
			List<ApiInfo> validateReult=new ArrayList();
			for(ApiInfo info:result) {
				if(info.getPath()==null||info.getPath().length==0) {
					log.warn("跳过:"+info.getMethodName());
					continue;
				}
				validateReult.add(info);
			}
			
			//根据方法所在类排序
			Collections.sort(validateReult,(info1,info2)->{
				
				return info1.getName().compareTo(info2.getName());
			});
			apiList = validateReult;
			return validateReult;
		} finally {
			lock.unlock();
		}
	}

	// 解析单个类
	private List<ApiInfo> parse(Class clazz) {
		//如果是代理类就用父类
		if(ClassUtil.isInstance(clazz, SpringProxy.class)) {
			clazz=clazz.getSuperclass();
		}
		
		List<ApiInfo> list = new ArrayList();
		for (Method method : clazz.getMethods()) {
			// 根据requestMapping,判断是否是接口方法
			ApiInfo info = createByMapping(method);
			if (info == null) {
				continue;
			}

			// 设置参数
			List<ApiParameter> params = getParam(method);
			info.setParams(params);

			// 返回值
			ApiResult result = getReturn(method);
			info.setResult(result);

			// 方法描述
			Update update = AnnotationUtils.findAnnotation(method, Update.class);
			Query query = AnnotationUtils.findAnnotation(method, Query.class);
			Page page = AnnotationUtils.findAnnotation(method, Page.class);
			if (update != null) {
				info.setPath(new String[] { update.path() });
				info.setDescription(update.name());
			} else if (query != null) {
				info.setPath(new String[] { query.path() });
				info.setDescription(query.name());
			} else if (page != null) {
				info.setPath(new String[] { page.path() });
				info.setDescription(page.name());
			}
			list.add(info);
		}
		RequestMapping mapping = AnnotationUtils.findAnnotation(clazz, RequestMapping.class);
		if (mapping != null) {
			for (ApiInfo info : list) {
				info.setParentPath(mapping.value());
			}
		}
		return list;
	}

	private ApiResult getReturn(Method method) {
		ApiResult result = new ApiResult();
		Class clazz = method.getReturnType();
		
		//如果返回的对象是Response,就找出具体的泛型类
		if(ClassUtil.isInstance(clazz, Response.class)){
			try {
				clazz=ClassUtil.getGenericClass(method.getGenericReturnType());
			}catch(Exception ex) {
				log.error(ex.getMessage(),ex);
				clazz=Object.class;
			}
			
		}
		
		result.setDataType(clazz);
		Return ret = method.getAnnotation(Return.class);
		if (ret != null) {
			result.setDescription(ret.value());
		}
		
		//如果是直接返回
		ResponseBody body=AnnotationUtils.findAnnotation(method, ResponseBody.class);
		result.setResponseBody(body!=null);
		return result;
	}

	// 获得参数信息
	private List<ApiParameter> getParam(Method method) {
		List<ApiParameter> apiPara = new ArrayList();

		Parameter[] params = method.getParameters();
		Param[] paramAnn = method.getAnnotationsByType(Param.class);
		Map<Parameter, Param> map = pair(params, paramAnn);
	
		Type[] type=method.getGenericParameterTypes();
		for(int i=0;i<params.length;i++) {
			Parameter pa=params[i];
			ApiParameter api = new ApiParameter();
			api.setClazz(pa.getType());
			api.setType(type[i]);
			api.setName(pa.getName());
			
			if(ClassUtil.isInstance(pa.getType(), Request.class)) {
				ApiParameter requestContent = new ApiParameter();
				Class realType=ClassUtil.getGenericClass(type[i]);
				requestContent.setClazz(realType);
				requestContent.setName("content");
				Remark rk=AnnotationUtils.getAnnotation(realType, Remark.class);
				if(rk!=null) {
					requestContent.setDescription(rk.value());
				}
				api.getConfigChildren().put("content", requestContent);
			}
			
			Param anno = map.get(pa);
			if (anno != null) {
				api.setDescription(anno.value());
			}

			apiPara.add(api);
		}
		return apiPara;
	}

	private Map<Parameter, Param> pair(Parameter[] params, Param[] paramAnn) {
		Map<Parameter, Param> map = new HashMap();
		// 如果个数相等,直接按顺序配对,否则按paramAnn设置的参数名
		if (params.length == paramAnn.length) {
			for (int i = 0; i < params.length; i++) {
				map.put(params[i], paramAnn[i]);
			}
		} else {
			for (int i = 0; i < params.length; i++) {
				String name = params[i].getName();
				for (int j = 0; j < paramAnn.length; j++) {
					if (name.equalsIgnoreCase(paramAnn[j].name())) {
						map.put(params[i], paramAnn[j]);
						break;
					}
				}
			}
		}
		return map;
	}

	private ApiInfo createByMapping(Method method) {
		RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
		if(mapping==null) {
			return null;
		}
		ApiInfo info = new ApiInfo();
		info.setContentType(mapping.produces());
		info.setRequestMethod(mapping.method());
		info.setPath(mapping.value());
		info.setFullName(method.getDeclaringClass().getName());
		info.setSimpleName(method.getDeclaringClass().getSimpleName());
		info.setMethodName(method.getName());
		info.setName(method.getDeclaringClass().getSimpleName()+"."+method.getName());
		
		if(method.getParameters().length==1) {
			RequestBody reqBody=AnnotationUtils.getAnnotation(method.getParameters()[0], RequestBody.class);
			info.setRequestBody(reqBody!=null);
		}
		
		
		
		return info;
	}

	public static List<String> getParameterNameJava8(Class clazz, String methodName) {
		List<String> paramterList = new ArrayList<>();
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			if (methodName.equals(method.getName())) {
				Parameter[] params = method.getParameters();
				for (Parameter parameter : params) {
					paramterList.add(parameter.getName());
				}

			}
		}

		return paramterList;
	}
}
